/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, AstTreeUtils, ts } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { FileMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { BaseChecker, BaseMetaData } from "../BaseChecker";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoUnsafeAssignmentCheck');
const gMetaData: BaseMetaData = {
  severity: 2,
  ruleDocPath: "docs/no-unsafe-assignment-check.md",
  description: "Disallow assigning a value with type `any` to variables and properties.",
};

export class NoUnsafeAssignmentCheck implements BaseChecker {
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const fileMatcherCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check
    }
    return [fileMatcherCb];
  }

  public check = (target: ArkFile) => {
    
    const originPath = target.getName();
    if (!this.isTsFile(originPath)) {
        return;
    }

    const severity = this.rule.alert ?? this.metaData.severity;
    const sourceFile: ts.SourceFile = AstTreeUtils.getASTNode(target.getName(), target.getCode());
    const visitNode = (node: ts.Node) => {
      // 检查变量声明
      if (ts.isVariableDeclaration(node)) {
        if (!this.checkAssignment(node.name, node.initializer, sourceFile)){
          const pos = this.getLineAndColumn(node.name);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      // 检查赋值表达式
      else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
        if(!this.checkAssignment(node.left, node.right, sourceFile)){
          const pos = this.getLineAndColumn(node.left);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      // 检查函数参数默认值
      else if (ts.isParameter(node) && node.initializer) {
        if (node.initializer.getText().includes('as any')) {
          const pos = this.getLineAndColumn(node.name);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      // 检查类属性初始化
      else if (ts.isPropertyDeclaration(node) && node.initializer) {
        if (node.initializer.getText().includes('as any')) {
          const pos = this.getLineAndColumn(node.name);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      // 检查属性赋值
      else if (ts.isPropertyAssignment(node) && node.initializer) {
        if(!this.checkAssignment(node.name, node.initializer, sourceFile)){
          const pos = this.getLineAndColumn(node.name);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      else if (node.kind === ts.SyntaxKind.AnyKeyword) {
        if (this.isInEqualsSign(node)) {
          const pos = this.getLineAndColumn(node);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
        }
      }

      // 递归遍历子节点
      ts.forEachChild(node, visitNode);
    }
    ts.forEachChild(sourceFile, visitNode);
  }

  /**
   * 检查节点的父节点是否为赋值语句
   */
  private isInEqualsSign(node: ts.Node): boolean {
    let mNode = node;
    while(mNode) {
      if (ts.isVariableDeclaration(mNode) || (ts.isBinaryExpression(mNode) && mNode.operatorToken.kind === ts.SyntaxKind.EqualsToken)) {
        return true;
      }
      if (ts.isSourceFile(mNode)) {
        return false;
      }
      mNode = mNode.parent;
    }
    return false;
  }

  private hasAnyOrUnknownDeep(typeNode: ts.TypeNode): boolean {
    // 当前节点是 any/unknown
    if (
      typeNode.kind === ts.SyntaxKind.AnyKeyword ||
      typeNode.kind === ts.SyntaxKind.UnknownKeyword
    ) {
      return true;
    }

    // 递归检查子节点（适用于联合类型、交叉类型等）
    let found = false;
    ts.forEachChild(typeNode, (child) => {
      if (ts.isTypeNode(child) && this.hasAnyOrUnknownDeep(child)) {
        found = true;
      }
    });
    return found;
  }

  /**
   * 判断节点是否包含显式的 `any` 或 `unknown` 类型注解
   * @param node 待检查的 AST 节点
   * @returns 是否包含 `any` 或 `unknown` 类型
   */
  private isAnyOrUnknownType(node: ts.Node): boolean {
    // 1. 提取类型注解节点
    let typeNode: ts.TypeNode | undefined;

    // 处理变量、函数参数、返回值等声明
    if (
      ts.isVariableDeclaration(node) ||
      ts.isParameter(node) ||
      ts.isFunctionDeclaration(node) ||
      ts.isPropertySignature(node) ||
      ts.isTypeAliasDeclaration(node)
    ) {
      typeNode = node.type;
    }
    // 处理类型断言（`as any`）
    else if (ts.isAsExpression(node)) {
      typeNode = node.type;
    } else if (
      node.kind === ts.SyntaxKind.ArrayLiteralExpression ||
      node.kind === ts.SyntaxKind.ObjectLiteralExpression || 
      node.kind === ts.SyntaxKind.SpreadElement || 
      node.kind === ts.SyntaxKind.SpreadAssignment || 
      node.kind === ts.SyntaxKind.ParenthesizedExpression || 
      node.kind === ts.SyntaxKind.NewExpression || 
      node.kind === ts.SyntaxKind.TypeReference || 
      node.kind === ts.SyntaxKind.ArrayType
    ) {
      let isAny = false;
      node.forEachChild((item: ts.Node)=>{
        if (isAny) {
          return;
        }
        isAny = this.isAnyOrUnknownType(item);
      });
      return isAny;
    } else if (
      node.kind === ts.SyntaxKind.AnyKeyword ||
      node.kind === ts.SyntaxKind.UnknownKeyword
    ) {
      return true;
    } else if (node.getText() === 'this') {
      return true;
    }

    return typeNode ? this.hasAnyOrUnknownDeep(typeNode) : false;
  }

  /**
   * 检查赋值操作是否安全
   * @param left 赋值目标的节点
   * @param right 赋值值的节点
   * @returns 是否安全 true安全，false不安全
   */
  private checkAssignment(left: ts.Node, right: ts.Node | undefined, sourceFile: ts.SourceFile): boolean {
    if (!right) return true;

    try {
      if (!this.isAnyOrUnknownType(left) && this.isAnyOrUnknownType(right)) {
        return false;
      }
    } catch(error) {
      logger.error(left.getText(sourceFile) + " = " + right.getText(sourceFile) + "\ntype检查失败：\n" + error);
    }
    return true;
  }

  private addIssueReport(pos: { line: number, startCol: number, endCol: number, filePath: string }, severity: number, message: string) {
    let defects = new Defects(pos.line, pos.startCol, pos.endCol, message, severity, this.rule.ruleId,
      pos.filePath, this.metaData.ruleDocPath, true, false, false);
    this.issues.push(new IssueReport(defects, undefined));
    RuleListUtil.push(defects);
  }
  
  private getLineAndColumn(node: ts.Node) {
    const sourceFile = node.getSourceFile();
    let start = node.getStart();
    let end = node.getEnd();
    if (node.modifiers) {
      start = node.modifiers[0].end + 1;
    }
    let parent = node.parent;
    if (ts.isVariableDeclaration(parent) && parent.initializer) {
      if (parent.initializer.kind === ts.SyntaxKind.ArrayLiteralExpression) {
        parent.initializer.forEachChild((item: ts.Node)=>{
          if (item.kind === ts.SyntaxKind.SpreadElement) {
            start = item.getStart();
            end = item.getEnd();
            return;
          }
        })
      }
    }
    const { line: textLine, character: textCharacter } = sourceFile.getLineAndCharacterOfPosition(start);
    const { line: endTextLine, character: endTextCharacter } = sourceFile.getLineAndCharacterOfPosition(end);
    return {
      line: textLine + 1, // 行号从1开始
      startCol: textCharacter + 1, // 列号从1开始
      endCol: endTextCharacter + 1, // 列号从1开始
      filePath: sourceFile.fileName
    };
  }

  private isTsFile(filePath: string): boolean {
    return filePath.toLowerCase().endsWith('.ts');
  }
}
